// Package config provides enhanced configuration management
package config

import (
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

// Manager interface for configuration management
type Manager interface {
	// Get methods
	Get(key string) interface{}
	GetString(key string) string
	GetInt(key string) int
	GetInt64(key string) int64
	GetBool(key string) bool
	GetDuration(key string) time.Duration
	GetStringSlice(key string) []string

	// Get with default methods
	GetStringDefault(key, defaultValue string) string
	GetIntDefault(key string, defaultValue int) int
	GetInt64Default(key string, defaultValue int64) int64
	GetBoolDefault(key string, defaultValue bool) bool
	GetDurationDefault(key string, defaultValue time.Duration) time.Duration

	// Environment methods
	GetEnv(key, defaultValue string) string
	MustGetEnv(key string) string

	// Validation
	IsSet(key string) bool
	Validate() error

	// Hot reload
	WatchConfig() error
	OnConfigChange(func())

	// Utilities
	AllSettings() map[string]interface{}
}

// viperManager implements Manager using Viper
type viperManager struct {
	mu     sync.RWMutex
	viper  *viper.Viper
	watchers []func()
}

// NewManager creates a new configuration manager
func NewManager(configPath string) (Manager, error) {
	v := viper.New()
	
	// Set config file path
	v.SetConfigName("app")
	v.SetConfigType("toml")
	v.AddConfigPath(configPath)
	v.AddConfigPath("./configs")
	v.AddConfigPath(".")

	// Environment variables
	v.AutomaticEnv()
	v.SetEnvPrefix("GIN_FRAMEWORK")

	// Set defaults
	setDefaults(v)

	// Read config file
	if err := v.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
			return nil, fmt.Errorf("failed to read config file: %w", err)
		}
		// Config file not found, using defaults and env vars
	}

	manager := &viperManager{
		viper: v,
	}

	return manager, nil
}

// setDefaults sets default configuration values
func setDefaults(v *viper.Viper) {
	// Application defaults
	v.SetDefault("app.name", "gin-framework")
	v.SetDefault("app.version", "1.0.0")
	v.SetDefault("app.debug", false)
	v.SetDefault("app.port", ":8080")
	v.SetDefault("app.wait_time", 5)
	v.SetDefault("app.max_header_bytes", 1<<20) // 1MB
	v.SetDefault("app.read_timeout", "10s")
	v.SetDefault("app.write_timeout", "10s")

	// Database defaults
	v.SetDefault("databases.type", "mysql")
	v.SetDefault("databases.host", "127.0.0.1")
	v.SetDefault("databases.port", "3306")
	v.SetDefault("databases.username", "root")
	v.SetDefault("databases.password", "")
	v.SetDefault("databases.database", "test")
	v.SetDefault("databases.debug", false)
	v.SetDefault("databases.max_open_conns", 25)
	v.SetDefault("databases.max_idle_conns", 5)
	v.SetDefault("databases.max_lifetime_conns", 300) // 5 minutes

	// Redis defaults
	v.SetDefault("redis.addr", "127.0.0.1:6379")
	v.SetDefault("redis.password", "")
	v.SetDefault("redis.db", 0)
	v.SetDefault("redis.pool_size", 10)
	v.SetDefault("redis.min_idle_conns", 3)

	// Logging defaults
	v.SetDefault("log.level", "info")
	v.SetDefault("log.path", "/tmp/log")
	v.SetDefault("log.name", "system")
	v.SetDefault("log.max_size", 100)    // megabytes
	v.SetDefault("log.max_backups", 3)
	v.SetDefault("log.max_age", 28)      // days
	v.SetDefault("log.compress", true)

	// JWT defaults
	v.SetDefault("jwt.timeout", "15m")
	v.SetDefault("jwt.max_refresh", "24h")
	v.SetDefault("jwt.realm", "gin-framework")

	// Rate limiting defaults
	v.SetDefault("rate_limit.requests_per_minute", 60)
	v.SetDefault("rate_limit.burst", 10)

	// Cache defaults
	v.SetDefault("cache.default_expiration", "5m")
	v.SetDefault("cache.cleanup_interval", "10m")
}

// Get returns the value associated with the key
func (m *viperManager) Get(key string) interface{} {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.Get(key)
}

// GetString returns the value associated with the key as a string
func (m *viperManager) GetString(key string) string {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.GetString(key)
}

// GetInt returns the value associated with the key as an integer
func (m *viperManager) GetInt(key string) int {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.GetInt(key)
}

// GetInt64 returns the value associated with the key as an int64
func (m *viperManager) GetInt64(key string) int64 {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.GetInt64(key)
}

// GetBool returns the value associated with the key as a boolean
func (m *viperManager) GetBool(key string) bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.GetBool(key)
}

// GetDuration returns the value associated with the key as a duration
func (m *viperManager) GetDuration(key string) time.Duration {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.GetDuration(key)
}

// GetStringSlice returns the value associated with the key as a slice of strings
func (m *viperManager) GetStringSlice(key string) []string {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.GetStringSlice(key)
}

// GetStringDefault returns the value or default if not set
func (m *viperManager) GetStringDefault(key, defaultValue string) string {
	if m.IsSet(key) {
		return m.GetString(key)
	}
	return defaultValue
}

// GetIntDefault returns the value or default if not set
func (m *viperManager) GetIntDefault(key string, defaultValue int) int {
	if m.IsSet(key) {
		return m.GetInt(key)
	}
	return defaultValue
}

// GetInt64Default returns the value or default if not set
func (m *viperManager) GetInt64Default(key string, defaultValue int64) int64 {
	if m.IsSet(key) {
		return m.GetInt64(key)
	}
	return defaultValue
}

// GetBoolDefault returns the value or default if not set
func (m *viperManager) GetBoolDefault(key string, defaultValue bool) bool {
	if m.IsSet(key) {
		return m.GetBool(key)
	}
	return defaultValue
}

// GetDurationDefault returns the value or default if not set
func (m *viperManager) GetDurationDefault(key string, defaultValue time.Duration) time.Duration {
	if m.IsSet(key) {
		return m.GetDuration(key)
	}
	return defaultValue
}

// GetEnv returns environment variable or default
func (m *viperManager) GetEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

// MustGetEnv returns environment variable or panics
func (m *viperManager) MustGetEnv(key string) string {
	value := os.Getenv(key)
	if value == "" {
		panic(fmt.Sprintf("Environment variable %s is required", key))
	}
	return value
}

// IsSet checks if key is set
func (m *viperManager) IsSet(key string) bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.IsSet(key)
}

// Validate validates the configuration
func (m *viperManager) Validate() error {
	// Validate required settings
	required := []string{
		"databases.host",
		"databases.database",
		"redis.addr",
	}

	for _, key := range required {
		if !m.IsSet(key) || m.GetString(key) == "" {
			return fmt.Errorf("required configuration key %s is missing", key)
		}
	}

	// Validate ranges
	if port := m.GetString("app.port"); port != "" && port[0] != ':' {
		return fmt.Errorf("app.port must start with ':'")
	}

	if waitTime := m.GetInt64("app.wait_time"); waitTime < 0 || waitTime > 300 {
		return fmt.Errorf("app.wait_time must be between 0 and 300 seconds")
	}

	return nil
}

// WatchConfig watches for config file changes
func (m *viperManager) WatchConfig() error {
	m.viper.WatchConfig()
	m.viper.OnConfigChange(func(e fsnotify.Event) {
		m.mu.Lock()
		watchers := make([]func(), len(m.watchers))
		copy(watchers, m.watchers)
		m.mu.Unlock()

		for _, watcher := range watchers {
			watcher()
		}
	})
	return nil
}

// OnConfigChange adds a config change callback
func (m *viperManager) OnConfigChange(callback func()) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.watchers = append(m.watchers, callback)
}

// AllSettings returns all settings
func (m *viperManager) AllSettings() map[string]interface{} {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.viper.AllSettings()
}